package cn.icanci.loopstack.ddk.client.server;

import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.api.client.Client;
import cn.icanci.loopstack.api.client.http.HttpClientImpl;
import cn.icanci.loopstack.ddk.client.properties.DdkProperties;
import cn.icanci.loopstack.ddk.common.result.R;
import cn.icanci.loopstack.ddk.common.socket.UriConstant;
import cn.icanci.loopstack.ddk.common.socket.publish.RegisterDTO;
import cn.icanci.loopstack.ddk.common.utils.IPUtils;

import java.util.concurrent.*;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import com.google.common.collect.Maps;

/**
 * @author icanci
 * @since 1.0 Created in 2023/01/06 22:30
 */
@Service
@SuppressWarnings("all")
public class RegisterServer implements InitializingBean {
    private static final Logger             logger         = LoggerFactory.getLogger(RegisterServer.class);
    /** http实例 */
    private static final Client             CLIENT         = HttpClientImpl.getInstance();
    @Resource
    private DdkProperties                   ddkProperties;

    private static final int                CORE_SIZE      = Runtime.getRuntime().availableProcessors();

    private static final ThreadPoolExecutor REGISTER_POOL  = new ThreadPoolExecutor(CORE_SIZE,             //
        CORE_SIZE << 1,                                                                                    //
        60L,                                                                                               //
        TimeUnit.SECONDS,                                                                                  //
        new LinkedBlockingQueue<>(2000),                                                                   //
        runnable -> new Thread(runnable, "RegisterClient Biz Pool-" + runnable.hashCode()),                //
        (r, executor) -> {
            throw new RuntimeException("RegisterClient Biz Pool is EXHAUSTED!");
        });

    /** 注册请求地址 */
    private static final String             REQ_URL_FORMAT = "http://%s:%s%s";

    /**
     * 将SDK所在服务注册到注册中心
     *
     * @param serverAddress 服务端ip地址
     * @param serverPort 服务端端口
     * @param clientPort 客户端端口
     */
    public void register(String serverAddress, int serverPort, int clientPort) {
        String appId = ddkProperties.getAppId();

        String[] addresses = serverAddress.split(",");

        // TODO 注册地址打散，防止压力放在同一个机器上
        for (String address : addresses) {
            try {
                FutureTask<R> task = new FutureTask<>(new RegisterCallable(address, clientPort, appId, serverPort));
                REGISTER_POOL.execute(task);
                R r = task.get(10, TimeUnit.SECONDS);
                if (r.isOk()) {
                    break;
                } else {
                    logger.error("Task Register Exception:{}", r.getMessage());
                }
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                logger.warn("Register Exception:{}", e.getMessage());
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (!ddkProperties.isLoad()) {
            return;
        }
        // 注入注册bean
        NamedServer.setRegisterService(this);

        // 启动服务器
        NamedServer.startClient(ddkProperties.getServerIps(), ddkProperties.getServerPort(), ddkProperties.getClientPort());
    }

    /** 注册器  */
    private static class RegisterCallable implements Callable<R> {

        private final String address;
        private final int    clientPort;
        private final String appId;
        private final int    serverPort;

        public RegisterCallable(String address, int clientPort, String appId, int serverPort) {
            this.address = address;
            this.clientPort = clientPort;
            this.appId = appId;
            this.serverPort = serverPort;
        }

        @Override
        public R call() throws Exception {
            RegisterDTO registerDTO = new RegisterDTO(IPUtils.getHostIpAddress(), clientPort, appId);
            String reqUrl = String.format(REQ_URL_FORMAT, address, serverPort, UriConstant.ToServer.REGISTER);

            Client.RpcRequest rpcRequest = new Client.RpcRequest(reqUrl, registerDTO, Maps.newHashMap(), Method.POST, 3, TimeUnit.SECONDS, 3);

            R call = CLIENT.call(rpcRequest, R.class);

            logger.info("[RegisterCallable][call] Register result:{}", JSONUtil.toJsonStr(call));

            return call;
        }
    }
}
